package com.zbb.online_exam.config;

import com.zbb.online_exam.dto.JsonResult;
import com.zbb.online_exam.filter.JwtAuthenticationTokenFilter;
import com.zbb.online_exam.utils.HttpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

//SpringSecurity配置类
@Configuration
//启用基于SpringSecurity的安全注解
@EnableWebSecurity
//启用服务方法中的SpEL，jsr250，secured三种类型的注解
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 全局的密码加密类，当前SpringSecurity推荐使用BCryptPasswordEncoder的加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 相当于XML中配置用户和权限的数据来源
     * 这里的JdbcDaoImpl实现的是UserDetailService接口
     * 表示从数据库获取用户和权限信息
     */
    @Bean("jdbcUseService")
    public JdbcDaoImpl userDetailService() {
        //创建JdbcDaoImpl对象
        JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
        //因为要操作数据库，所以要指定数据源
        //因为导入了jdbc-starter，所以直接使用JdbcTemplate对象即可
        //Druid和JdbcTemplate在Spring容器中自动装配完成
        jdbcDao.setJdbcTemplate(jdbcTemplate);
        //查询用户名和密码的SQL语句
        jdbcDao.setUsersByUsernameQuery("select username,password,status as enabled from user where username=?");
        //查询用户名和其对应权限名的SQL语句
        jdbcDao.setAuthoritiesByUsernameQuery("select\n" +
                "                u.username,r.name as authority\n" +
                "                from user u\n" +
                "                join user_role ur\n" +
                "                on u.id=ur.user_id\n" +
                "                join role r\n" +
                "                on r.id=ur.role_id\n" +
                "                where u.username=?");
        return jdbcDao;
    }



    //注入JdbcTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //注入用户权限数据源对象
    @Autowired
    //因为在SpringBoot环境中UserDetailsService在实现Bean不止一个，所以通过id加以区分
    @Qualifier("jdbcUseService")
    private UserDetailsService userDetailsService;


    /**
     * 将用户权限数据源和密码加密器放入认证管理器中
     * SpringSecurity中进行认证和授权时会用到
     *
     * @param authenticationManagerBuilder
     */
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
        try {
            authenticationManagerBuilder
                    // 设置UserDetailsService
                    .userDetailsService(userDetailsService)
                    // 使用BCrypt进行密码的hash
                    .passwordEncoder(new BCryptPasswordEncoder());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 由于登录操作是创建JWT的操作
     * 这里主要设置登录操作对应的路径执行时不用走SpringSecurity的过滤链条
     * 直接使用自己编写的Controller方法进行登录创建即可
     */
    @Override
    public void configure(WebSecurity web) throws Exception {

        //登录请求完全不走SpringSecurity，由自定义Controller完成
        //其它不想走过滤链的请求也可以在这里配置
        //   /download/* 下载操作
        web.ignoring().mvcMatchers("/users/login", "/test/test01","/download")
                //忽略swagger
                .mvcMatchers("/swagger-ui.html")
                .mvcMatchers("/webjars/**")
                .mvcMatchers("/v2/**")
                .mvcMatchers("/swagger-resources/**")
                //忽略 Ueditor
                .mvcMatchers("/enterUpload")
                //忽略下载
                .mvcMatchers("/page/**")
                .mvcMatchers("/static/**")
                .mvcMatchers("/static/**")
                .mvcMatchers("/js/**")
                .mvcMatchers("/layui/**")
                .mvcMatchers("/ueditor1_4_3_1-utf8-jsp/**")
                //忽略页面 /favicon.ico
                .mvcMatchers("/favicon.ico")
        ;

    }

    /**
     * SpringSecurity中具体的访问策略配置
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        //关闭session和csrf
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        httpSecurity.csrf().disable();

        //支持跨域
        httpSecurity.cors();

        //配置Url访问路径以及具体的访问权限
        //这里底层处理时自动为指定的角色前面添加1了ROLE_,因此这里的角色名不需要再加ROLE_
        httpSecurity.authorizeRequests()
                //放行swagger
                .antMatchers("/swagger-ui.html").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/v2/**").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                //放行下载
                .antMatchers("/download").permitAll()
                .antMatchers("/download/*").permitAll()
                //放行Ueditor
                .antMatchers("/enterUpload").permitAll()

                .antMatchers("/page/**").permitAll()

                .antMatchers("/static/**").permitAll()

                .antMatchers("/js/**").permitAll()

                .antMatchers("/layui/**").permitAll()

                .antMatchers("/ueditor1_4_3_1-utf8-jsp/**").permitAll()

                .antMatchers("/favicon.ico").permitAll()

                //测试用的
                .mvcMatchers("/test/*").hasAnyRole("ADMIN", "STUDENT", "TEACHER");
        //下面也可以继续配置一些其它路径的访问策略
        //.mvcMatchers("").hasRole("");
        //如果有一些权限配置精确到业务层方法级别，则使用服务层方法保护相关的注解

        //定义异常处理器
        //默认情况下403,401等错误是直接通过页面转发完成的
        //前后端分离的项目中如果出现错误，基本都是通过JSON写出
        httpSecurity
                .exceptionHandling()
                //这里覆盖的就是当访问权限不够时写出JSON的操作，以此覆盖默认的转发方式
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    JsonResult result = new JsonResult();
                    result.setCode(403);
                    result.setError("权限不足");
                    HttpUtils.writeJson(response, result, result.getCode());
                });

        // 添加JWT过滤器
        // 用于在每次具体请求时判断当前请求携带的JWT是否为空、格式是否合法、是否过期……
        //以及该JWT中的用户信息和数据库中的是否保持一致
        httpSecurity
                .addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

    }

}
